package org.loong.crypto.service.software.provider.asymmetric.rsa;

import java.security.interfaces.RSAPublicKey;
import java.util.Objects;
import java.util.Optional;

import org.loong.crypto.common.exception.CryptoException;
import org.loong.crypto.common.exception.InvalidKeyException;
import org.loong.crypto.common.exception.InvalidKeyLengthException;
import org.loong.crypto.core.algorithm.CipherAlgorithm;
import org.loong.crypto.core.params.CipherParameters;
import org.loong.crypto.core.utils.RSAKeyUtils;
import org.loong.crypto.service.core.provider.Encrypter;
import org.loong.crypto.service.core.provider.impl.RSACipherProvider;

import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;

public class RSAEncrypter extends RSACipherProvider implements Encrypter {

    /**
     * The public key.
     */
    private RSAPublicKey publicKey;

    /**
     * The rsa encrypt algorithm.
     */
    private String rsaAlgorithm;
    
    /**
     * The rsa key length.
     */
    private Integer keyLength;
    
    /**
     * Creates a new encrypter.
     *
     * @param keyBytes the public key bytes. Must not be {@code null}.
     */
    public RSAEncrypter(final byte[] keyBytes) {
        this(RSAKeyUtils.toRSAPublicKey(keyBytes));
    }

    /**
     * Creates a new encrypter.
     *
     * @param publicKey the public key. Must not be {@code null}.
     */
    public RSAEncrypter(final RSAPublicKey publicKey) {
        if (publicKey == null) {
            throw new InvalidKeyException("The public RSA key must not be null");
        }

        if (!"RSA".equalsIgnoreCase(publicKey.getAlgorithm())) {
            throw new InvalidKeyException("The public key algorithm must be RSA.");
        }

        this.publicKey = publicKey;
    }

    @Override
    public void init(CipherParameters parameters) {
        if (Objects.nonNull(parameters.getEncryptionMethod())) {
            this.rsaAlgorithm = parameters.getEncryptionMethod().getAlgorithm();
            this.keyLength = parameters.getEncryptionMethod().getKeyLength();
        }
    }
    
    @Override
    public byte[] encrypt(CipherAlgorithm algorithm, byte[] data) throws CryptoException {
        // key size in bits
        int bitLength = publicKey.getModulus().bitLength();
        if (Objects.nonNull(keyLength) && bitLength < keyLength) {
            throw new InvalidKeyLengthException("The key length must be at least " + keyLength + " bits.");
        }
        
        try {
            RSA rsa = new RSA(Optional.ofNullable(rsaAlgorithm).orElse(algorithm.getName()), null, publicKey);
            return rsa.encrypt(data, KeyType.PublicKey);
        } catch (cn.hutool.crypto.CryptoException e) {
            throw new CryptoException("RSA encrypt exception: " + e.getMessage(), e);
        }
    }
}
